5. Transactions
A queued service typically queues up the response as
part of the incoming playback transaction. Given the queued service
definition of Example 8, Figure 2 depicts the
resulting transaction and the participating actions.
Example 8. Queuing up a response as part of the playback
transaction
class MyCalculator : ICalculator
{
[OperationBehavior(TransactionScopeRequired = true)]
public void Add(int number1,int number2)
{
...
try
{
...
}
catch //Do not rethrow
{
...
}
finally
{
CalculatorResponseClient proxy = new CalculatorResponseClient();
proxy.OnAddCompleted(result,error);
proxy.Close();
}
}
}
|
Design-wise, the nice thing about having the queued call
playback and the queued response in the same transaction is that if
the playback transaction is aborted for whatever reason (including due
to other services in the transaction aborting), the response is
canceled automatically. This is by far the most common choice for most
applications.
Note in Example 8 that the service
catches all exceptions and does not rethrow them. This is important,
because any unhandled exception (or rethrown exception) will abort the
response, so there won’t be any point in the service bothering to
respond. Using a response service intrinsically means that the service
does not rely on the automatic retry mechanism of WCF, and it handles
its own business logic failures because the clients expect it to
respond in a prescribed manner.
5.1. Using a new transaction
As an alternative to always having the response be part of the
playback transaction, the service can respond in a new transaction
by encasing the response in a new transaction scope, as shown in
Example 9 and illustrated
in Figure 3.
Example 9. Responding in a new transaction
class MyCalculator : ICalculator
{
[OperationBehavior(TransactionScopeRequired = true)]
public void Add(int number1,int number2)
{
...
finally
{
using(TransactionScope transactionScope =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
CalculatorResponseClient proxy = new CalculatorResponseClient();
proxy.OnAddCompleted(result,error);
proxy.Close();
}
}
}
}
|
Responding in a new transaction is required in two cases. The
first is when the service wants to respond regardless of the outcome
of the playback transaction (which could be aborted by other
downstream services). The second case is when the response is nice
to have, and the service does not mind if the playback transaction
commits but the response aborts.
5.2. Response service and transactions
Since a response service is just another queued service, the
mechanics of managing and participating in a transaction are just
like those of any other queued service. However, there are a few
points worth mentioning in this particular context. The response
service can process the response as part of the incoming response
playback transaction:
class MyCalculatorResponse : ICalculatorResponse
{
[OperationBehavior(TransactionScopeRequired = true)]
public void OnAddCompleted(...)
{...}
}
This is by far the most common option, because it allows for
retries. That said, the response service should avoid lengthy
processing of the queued response, because it may risk aborting the
playback transaction. The response service can process the response in a separate transaction if
the response is nice to have (as far as the provider of the response
service is concerned):
class MyCalculatorResponse : ICalculatorResponse
{
public void OnAddCompleted(int result,ExceptionDetail error)
{
using(TransactionScope scope = new TransactionScope())
{...}
}
}
When the response is processed in a new transaction, if that
transaction aborts, WCF will not retry the response out of the
response service’s queue. Finally, for response processing of long
duration, you can configure the response service not to use a
transaction at all (including the playback transaction):
class MyCalculatorResponse : ICalculatorResponse
{
public void OnAddCompleted(...)
{...}
}